package com.chaudcast;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.microedition.io.Connection;
import javax.microedition.io.HttpConnection;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;

public class Network {
	/** The max size to be read from the stream at one time */
    public static int READ_CHUNK = 340; // bytes
    
    /** Stream remote address */
    private static String _host;
    private static int _port;
    private static String _uri;
	
    /**
     * The minimum number of bytes that must be buffered before the media file
     * will begin playing.
     */
    private static int _startBuffer=256000;
    public static Object _startBufferMutex = new Object();
    
    /**
     * The minimum forward byte buffer which must be maintained in order for
     * the audio to keep playing. If the forward buffer falls below this
     * number, the playback will pause until the buffer increases.
     */
    public static int _pauseBytes;

    /**
     * The minimum forward byte buffer required to resume
     * playback after a pause.
     */
    public static int _resumeBytes;
    
    /** A boolean flag indicating that the thread should pause
    * until the buffer has increased sufficiently.
    */
    public static boolean _paused;
    public static Object _pauseMutex = new Object();
    
    
    /** The media buffer */
    private static CircularByteBuffer _buffer = new CircularByteBuffer(_startBuffer*4);
    
    /** The connection thread */
    private static ConnectionThread _reader = null;
    
    /** The connection */
    private static StreamConnection _connection = null;
    
    /** The InputStream */
    private static InputStream _stream = null;
    
    /** A counter for the total number of buffered bytes */
    private static volatile int _totalRead=0;
    
    
    /**
     * A flag used to indicate that the initial buffering is complete. In
     * other words, that the current buffer is larger than the defined start
     * buffer size.
     */
    private static volatile boolean _bufferingComplete;
   
    /**
     * A flag used to indicate that the streaming should stop
     */
    public static boolean _stop = true;
    
    /**
     * Establishes a connection to the server and returns a
     * the header received from the remote host
     */
    public static String open(String url) throws IOException {
		disconnect();
		
    	/* get remote location */
    	_host = Util.getHost(url);
    	_port = Util.getPort(url);
    	_uri = Util.getUri(url);
    	
    	/* Open the connection to the remote stream */
    	_connection = (StreamConnection)connect(_host,_port);
    	System.out.println("Connected.");
    	
    	/* allow reading */
    	_stop = false;
    	_bufferingComplete=false;
    	_buffer.clear();
    	
    	/* Send a request and return response */
    	String response = request(_connection, _uri);
    	return response;
    }
    
    /**
     * Establishes a connection to the server and retrieves
     * a byte array with the contents
     */
    public static byte[] get(String url) {
		return null;
   	
    }
    
    /**
     * Starts a ConnectionThread array to fill the
     * circular buffer
     */
    public static void load() throws IOException{
    	_stop = false;
    	
    	/* check if connection is alive */
    	if (_stream == null) {
    		disconnect();
    		_connection = (StreamConnection)connect(_host,_port);
    		if (_connection!= null) {
    			/* Send a request */
    			System.out.println("Sending a request...");
    			ChaudCast.updateStatus("Sending a request...");
    			request(_connection, _uri);
			} else {
				System.out.println("ERROR: CAN'T OPEN CONNECTION..");
				ChaudCast.updateStatus("Error...");
				return;
			}
    	}

		_reader = new ConnectionThread(_stream, _buffer);
    	if (_reader!=null) {
    		_reader.start();
    	} else {
    		System.out.println("ERROR: CAN'T START READER...");
    		ChaudCast.updateStatus("Error...");
    		return;
    	}
    } // close load()
    
    /**
     * Returns data from the byte array if available
     */
    public static int read(byte[] bytes, int off, int len) {
    	//ChaudCast.updateTotal(_totalRead/1024);
    	
    	if (!_bufferingComplete) {
            ChaudCast.updateStatus("Initial Buffering...");
            synchronized(_startBufferMutex){
            	while (!_bufferingComplete){
            		try {
            			_startBufferMutex.wait();
            		} catch (InterruptedException iex) {
            			return 0;
            		}
            	}
            }
        }
    	
    	if (_paused) {
    		ChaudCast.updateStatus("Buffering...");
            synchronized(_pauseMutex){
            	while (_paused){
            		try {
            			_pauseMutex.wait();
            		} catch (InterruptedException iex) {
            			return 0;
            		}
            	}
            }
    	}
    	
        try {
			return _buffer.getInputStream().read(bytes, off, len);
		} catch (IOException e) {
			System.out.println(e);
			ChaudCast.errorDialog(""+e);
			return 0;
		}
    }
    /* Close and cleanup all readers and connections */
    public static void close() {
    	/* disable streaming */
    	_stop = true;
    	disconnect();
    }
    
    /**
     * Deals with dropped connections by going through the array of
     * ConnectionThreads and removing the dead ones
     */
    public static void connectionDropped(IOException ioe) {
		if (!_stop) {
			/* print out the error for debugging */
			System.out.println(ioe);
			ChaudCast.updateStatus("Connection dropped.");
			close();
			/* try to reestablish the connection */
			try {
				load();
			} catch (IOException e) {
				System.out.println("Connection cannot be recovered: "+e);
			}
		}
    }
    
    /**
     * Resizes the circular buffer
     */
    public static void bufferFull(IOException ioe) {
		System.out.println("The buffer is full.");
		ChaudCast.errorDialog("Buffer overflow.");
    	
    }

    /**
     * Getter and setter for _bufferingComplete 
     */
    public static void bufferingComplete(boolean bufferingComplete) {_bufferingComplete=bufferingComplete; }
    public static boolean bufferingComplete() {return _bufferingComplete; }
    
    /**
     * Getter and setter for _startBuffer 
     */
    public static int getStartBuffer() { return _startBuffer;}
    public static void setStartBuffer(int startBuffer) { _startBuffer=startBuffer;}
    
    /**
     * Setter for _pauseBytes 
     */
    public static void setPauseBytes(int pauseBytes) { _pauseBytes=pauseBytes;}
    
    /**
     * Setter for _resumeBytes 
     */
    public static void setResumeBytes(int resumeBytes) { _resumeBytes=resumeBytes;}
    
    /**
     * Increment total kbytes 
     */
    public static void addBytesRead(int bytesRead) { 
    	_totalRead += bytesRead;
    }
    
    /**
     * Establish a Socket connection
     * Returns a Connection
     * @throws IOException 
     */
    private static Connection connect(String host, int port) throws IOException{
    	Connection conn = Connector.open("socket://"+host+":"+port+";deviceside=true");
    	ChaudCast.updateStatus("Opened connection.");
    	return conn;
    }
    
    /**
     * Send a GET request over a Socket connection
     * Returns a response
     * @throws IOException 
     */
    private static String request(StreamConnection connection, String object) throws IOException{
    	String result="";
    	InputStream responseStream;
    	OutputStream requestStream;
    	int readByte; // a byte read from the input stream
    	boolean stop = false; // used to determine when the header has been read
    	boolean nextStop = false; // used to determine when the header has been read
    	
    	// send request
    	requestStream = connection.openOutputStream();
    	if (requestStream != null) {
    		requestStream.write((new String("GET "+object+" HTTP/1.0\n\n")).getBytes());
    		requestStream.close();
    	}
    	// get response stream needed to parse headers
    	responseStream = connection.openInputStream();
    	
    	if (responseStream != null) {
    		while (-1 != (readByte = responseStream.read()) && !stop) {
    			//determine if we are done reading the buffer
    			if ((char)readByte == '\r') {
    				if (nextStop) stop = true;
    			} else if ((char)readByte == '\n') {
    				nextStop = true;
    			} else {
    				nextStop = false;
    			}
    			result += (char)readByte;
    		}
    		_stream = responseStream;
    	}
    	return result;
    }
    
    /**
     * Terminates all opened connections
     * and the reader thread 
     */
    private static void disconnect() {
    	/* try to close the reader */
		if (_reader != null) {
			try {
				System.out.println("Stopping reader...");
				ChaudCast.updateStatus("Stopping reader...");
				try {
					_reader.stop();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					System.out.println(e);
					ChaudCast.errorDialog(""+e);
				}
				_reader.interrupt();
				_reader = null;
			} catch (NullPointerException npe) {;}
		}
    	
		/* try to close the stream */
		if (_stream != null) {
			try {
				System.out.println("Closing stream...");
				ChaudCast.updateStatus("Closing stream...");
				_stream.close();
				_stream = null;
			} catch (IOException e) {
				System.out.println("IOException: "+e);
				ChaudCast.updateStatus("Stream can't stop.");
			} catch (NullPointerException npe) {;}
		}
    	/* try to close the connection */
    	if (_connection != null) {
    		try {
    			System.out.println("Closing connection...");
    			ChaudCast.updateStatus("Closing connection...");
    			_connection.close();
    			_connection = null;
    		} catch (IOException e) {
    			System.out.println("IOException: "+e);
    			ChaudCast.updateStatus("Connection cannot be closed.");
    			
    		}
    	}
    }
}
